-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Have collections impl Extend<&T> where T: copy #839
Have collections impl Extend<&T> where T: copy #839
Conversation
``` | ||
/// A type growable from an `Iterator` implementation | ||
pub trait Extend<T> { | ||
fn extend<It: Iterator<Item=T>, I: IntoIterator<IntoIter=It>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be fine just dropping this definition entirely from the RFC, I think the definition below should work shortly as well.
This sounds like a fantastic idea to me, although we would need to move quickly as it is a breaking change to the |
I believe @aturon always intended everything to work with IntoIterator. Extend not working with it is simply an oversight. Also I don't think it's a backcompat hazard because T: Iterator is IntoIterator. |
In the trait sense I think it may break code (due to the trait definition changing). For example this is invalid code: trait Foo {
fn foo<I: IntoIterator>(i: I);
}
impl Foo for i32 {
fn foo<I: Iterator>(i: I) { ... }
} |
Ah yes, of course. Implementors would break. |
It seems that the |
Right, the plan was to migrate everything to I'm (obviously) on board with this RFC :-) |
I've updated the text to simply assume that Extend uses IntoIterator. I am currently testing a patch to do exactly that. |
I started writing a patch for this, and it now occurs to me that we could instead provide these blanket impls and call it a day:
This would reduce distributable bloat, and also just be less work. Also gives free impls to everyone. Haven't tested that this doesn't conflict with some coherence. |
Compiler rejects those blankets as conflicting with the concrete implementations. 😢 |
I'm not sure if I like the implicit Clones that occur here, we don't have a lot of precedent for that with Iterator operations. I'd rather type |
@reem would you be happy if this was restricted to Copy? |
Sure. We have extreme precedent for implicit Copy but less for implicit Clone, especially as relates to iterators. EDIT: Cleanup email mess. |
+1 for restricting to Copy instead of Clone |
Hm, I'm not sure I agree about the implicit |
So now that we're taking // before this RFC
foo.extend([1, 2, 3].iter().cloned());
foo.extend(borrowed_hash_set.iter().cloned());
foo.extend(owned_hash_set);
// after this RFC
foo.extend(&[1, 2, 3]);
foo.extend(borrowed_hash_set);
foo.extend(owned_hash_set) I think that I personally find the "noise level" tolerable, but an interesting data point I just thought of was that if we allow this sort of "clone without writing clone" then it seems that we should seriously consider implicit cloning on All that basically surmounts of me thinking that we should tread carefully into the domain of "cloning without |
I've added Copy-only as an explicit alternative. |
To push back on this a little bit, it's far from the case that explicit client-side OTOH, usually it's pretty obvious from the types involved in methods like this whether a clone is involved. If I'm appending an iterator over So for me, the guideline about I would vote for a |
I'd say it's pretty easy to miss in the context of gluing code together. An errant Worse for a newbie, they don't have the compiler slap them on the wrist for getting their ownership wrong. It just chugs along fine. This is totally fine for Copy types though. The semantic and performance difference between T and *&T for Copy types is basically nil in the case of Extend. |
Hm, I find both @aturon and @gankro convincing.
This is a good point, but I'm also somewhat wary in that
I do agree that it's easy to start Definitely seems like a fine line here! I think I'm leaning more towards |
I think this is mostly fine, locally: that is, assuming the rest of the program is correct, and you have the task "add these elements to this data structure", then However, a lot of the time, the rest of the program isn't correct, e.g. function signatures may not be taking ownership where they should be. Having this lots of implicit copying will disguise these points in a design that can be improved, by not forcing the ownership to be explicit in the code. I'm not sure what balance between local ergonomics and global ergonomics suits Rust best. |
From discussion in #rust-libs: we realized that there are some interesting ramifications in the generic programming context. In particular, if you bound by To me, this suggests that this should actually be a distinct method -- we could consider making it a defaulted method on the |
Some follow-up on the previous comment, via @bluss: the generic code itself probably wouldn't be able to tell the difference without an additional bound (that allowed it to read out of the collection). That bound would distinguish the two cases. Nevertheless, providing a separate defaulted method could help resolve the |
This of course can't work for both Maps and Sequences &T vs (&K, &V). Sequences are arguably more desirable? |
We could have an associated type, but that might be going too far. (Or of course a separate trait, but that has discoverability and complexity downsides.) |
I agree that something like this is necessary, as the current |
Personally I think this idea has too many issues with it. It makes some concrete case more ergonomic, but adds some curiosities in the generic context and raises the issue of how expensive an implicit OP can be. I'd advise closing this RFC pending letting the language/ecosystem mature more, unless people vociferously demand this ASAP. |
I think I come down close to where @gankro is. A separate |
So to expand on my comment:
Unless we propose upgrading |
@gankro Nevermind, you're right: you'd need HKT to make that work. There's still the option of a separate trait, though. |
What is wrong with the fact that 'pushing' &Ts to a Vec would be different from pushing &Ts to a Vec<&T>? Surely no one expects a Vec to take in an &T without duplicating it? I think I'm not understanding the implications of the fact that a generic bounded by Extend<&'a T> would implement different behavior depending on the parameter? Every implementation has different behavior. |
So this is my current stance on this RFC:
Therefore I lean to merging this RFC with the text changed to s/Clone/Copy (and adjust the text around push_all accordingly), with an eye to upgrading to Clone in the future once we've had some time to "test the waters". (You can see in the PR history that I kept starting to do this and then waffling as I started to edit the text) |
Oh, in case it wasn't clear, this RFC has entered its Final Comment Period as of yesterday. |
@gankro 👍 |
👍 from me as well, I hope we do get to |
Thumbs up, bstrie needs ergonomy badly |
I have updated the text of the RFC to reflect the agreed upon design, and why it was taken. |
This RFC appears to have quite broad consensus at this point, so I'm going to merge it. The extension of moving the bound from Thanks again for the RFC @gankro! |
`FromIterator<T>` and `Extend<T>` are implemented for the following values of `T`: - `char` for `StrTendril`; - `u8` for `ByteTendril`; - `&u8` for `ByteTendril`, for reasons of ergonomics (see also rust-lang/rfcs#839 for justification).
`FromIterator<T>` and `Extend<T>` are implemented for the following values of `T`: - `char` for `StrTendril`; - `u8` for `ByteTendril`; - `&u8` for `ByteTendril`, for reasons of ergonomics (see also rust-lang/rfcs#839 for justification); - `&str` for `StrTendril`; - `&[u8]` for `ByteTendril`; - `&Tendril<F>` for `Tendril<F>` (the `Extend` implementation already existed; the `FromIterator` is added).
`FromIterator<T>` and `Extend<T>` are implemented for the following values of `T`: - `char` for `StrTendril`; - `u8` for `ByteTendril`; - `&u8` for `ByteTendril`, for reasons of ergonomics (see also rust-lang/rfcs#839 for justification); - `&str` for `StrTendril`; - `&[u8]` for `ByteTendril`; - `&Tendril<F>` for `Tendril<F>` (the `Extend` implementation already existed; the `FromIterator` is added).
…onSapin Add FromIterator and Extend implementations `FromIterator<T>` and `Extend<T>` are implemented for the following values of `T`: - `char` for `StrTendril`; - `u8` for `ByteTendril`; - `&u8` for `ByteTendril`, for reasons of ergonomics (see also rust-lang/rfcs#839 for justification); - `&str` for `StrTendril`; - `&[u8]` for `ByteTendril`; - `&Tendril<F>` for `Tendril<F>` (the `Extend` implementation already existed; the `FromIterator` is added). <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/tendril/15) <!-- Reviewable:end -->
Make all collections
impl<'a, T: Copy> Extend<&'a T>
.This enables both
vec.extend(&[1, 2, 3])
, andvec.extend(&hash_set_of_ints)
. This provides a more expressive replacement forVec::push_all
with literally no ergonomic loss, while leveraging established APIs.rendered